<script setup>
import { ref, computed, watch, nextTick, defineEmits, defineProps, onMounted } from 'vue'
import { useInfiniteScroll } from '@vueuse/core'
import { scrollToBottom, useAutoScrollBottom } from '@/hooks/auto-scroll-bottom'

const emit = defineEmits(['stop-receive', 'reply-again'])

const props = defineProps({
  load: Function,
  uniqueKey: String,
  displayStyle: String,
})

const messages = ref([]) //记录列表
const message = defineModel('message') //新纪录
const pageSize = 10 //每页加载条数

const startId = computed(() => messages.value[0]?.id ?? 0)
const lastMessage = computed(() => messages.value[messages.value.length - 1] ?? null)

//是否允许加载更多
const canLoadMore = ref(true)
const loading = ref(false)

const scrollbarRef = ref(null)
const messagesRef = ref(null)
const messagesL2Ref = ref(null)

const currentScroll = { height: 0, top: 0 }

//自动滚动到底部hook
const { onUpdateElement, onFinishElement, onScroll, resetAutoToBottom } =
  useAutoScrollBottom(scrollbarRef)

const showWelcomeMessage = computed(
  () => !lastMessage.value && !canLoadMore.value && !loading.value && !message.value
)

const onLoadMore = async () => {
  if (canLoadMore.value) {
    canLoadMore.value = false
    loading.value = true
    try {
      //加载记录
      const paginator = await props.load(startId.value, pageSize)
      //paginator.data数组反转
      paginator.data.reverse()
      //加入列表，插入头部
      messages.value.unshift(...paginator.data)
      //节流延迟，加载的数据如果小于10，表示没有更多数据
      setTimeout(() => (canLoadMore.value = paginator?.data.length === pageSize), 300)
    } finally {
      //组件渲染结束后，滚动到底部
      nextTick(() => (loading.value = false))
    }
  }
}

//清空上下文后清除可视区域内文字（即增加底部空白）
const addMinHeight = () => {
  //找histories里找最后一个
  if (lastMessage.value && lastMessage.value.prompt.next_anew === 1) {
    //获取messageRef的高度
    const messageHeight = messagesL2Ref.value.clientHeight
    //获取窗口高度
    const windowHeight = window.innerHeight
    //messageRef增加最小高度(增加当前屏幕高度的80%)，让可视区域空白
    messagesRef.value.style.minHeight = messageHeight + windowHeight * 0.6 + 'px'

    scrollToBottom(scrollbarRef.value)
  }
}

/**
 * 初始化页面内容
 * @param isClear 是否清除数据
 */
const initData = (isRemoveData = false) => {
  messages.value = []
  message.value = null
  canLoadMore.value = true
  messagesRef.value.style.minHeight = 'auto'
  if (isRemoveData) {
    canLoadMore.value = false
  } else {
    onLoadMore()
  }
}

/**
 * 清除上下文
 */
const clearContext = () => {
  messages.value[messages.value.length - 1].prompt.next_anew = 1
}

//监控 key 的变化（表示切换了对话），重置数据
watch(
  () => props.uniqueKey,
  () => initData()
)

//监控message的变化
watch(
  message,
  () => {
    const status = message.value?.receive?.status
    switch (status) {
      case 'waiting':
        scrollToBottom(messagesRef.value) // 滚动到底部，等待系统回复
        resetAutoToBottom()
        break
      case 'receiving':
        onUpdateElement()
        break
      case 'cancel':
      case 'finish':
      case 'error':
        onFinishElement()
        messages.value.push(message.value)
        message.value = ''
        break
    }
  },
  { deep: true }
)

//监控loading变化，自动化判断获取数据前后需要的页面变化
watch(loading, (newVal) => {
  //loading开始
  if (newVal) {
    if (scrollbarRef.value) {
      //开始加载新数据时，记录滚动条位置，用于上滑加载数据时记住滚动条位置
      currentScroll.height = scrollbarRef.value.scrollHeight
      currentScroll.top = scrollbarRef.value.scrollTop
    }
  } else {
    //loading结束
    const isFirst = messages.value.length <= pageSize
    //首次获取数据完成时，滚动条滚动到最底部
    if (isFirst) {
      addMinHeight() //如果第一条需要清除上下文，那么就给页面增加空白区域
      scrollToBottom(scrollbarRef.value)
    } else {
      //滚动条保持在当前可视
      const addedHeight = scrollbarRef.value.scrollHeight - currentScroll.height
      scrollbarRef.value.scrollTop = currentScroll.top + addedHeight
    }
  }
})

//监控最后一条消息
watch(
  lastMessage,
  () => {
    if (lastMessage.value) {
      //增加了清除上下文标记后，增加空白区域
      if (lastMessage.value.prompt.next_anew === 1) addMinHeight()
    }
  },
  { deep: true }
)

onMounted(() => {
  //无限滑动,加载数据
  useInfiniteScroll(scrollbarRef, () => onLoadMore(), {
    direction: 'top',
    distance: 10,
    canLoadMore: () => canLoadMore.value,
  })
})

//暴露接口
defineExpose({
  initData,
  clearContext,
  lastMessage,
  scrollToBottom: () => scrollToBottom(scrollbarRef.value),
})
</script>

<template>
  <!-- 聊天记录 -->
  <div ref="scrollbarRef" class="scrollbar" @scroll="onScroll">
    <slot name="header" />
    <div
      ref="messagesRef"
      class="mx-auto px-3 sm:px-4 w-full space-y-4 first:mt-3 last:mb-3 min-h-[90%] text-sm sm:text-base"
    >
      <div ref="messagesL2Ref">
        <ChatWelcomeMessage
          v-if="showWelcomeMessage"
          :role="$attrs.role"
          :welcome-text="$attrs['welcome-text']"
          :example-text="$attrs['example-text']"
        ></ChatWelcomeMessage>
        <el-divider v-if="canLoadMore" border-style="dashed">
          {{ loading ? '正在加载...' : '加载更多数据' }}
        </el-divider>
        <template v-for="msg in messages" :key="msg.id">
          <ChatMessage
            :display="props.displayStyle"
            :role="$attrs.role"
            :prompt="msg.prompt"
            :receive="msg.receive"
            @reply-again="$emit('reply-again', msg.prompt)"
          />
        </template>
        <ChatMessage
          v-if="message"
          :display="props.displayStyle"
          :role="$attrs.role"
          :prompt="message.prompt"
          :receive="message.receive"
        />
      </div>
    </div>
    <slot name="footer"></slot>
  </div>
</template>

<style scoped>
.scrollbar {
  @apply relative flex-1 bg-[--el-fill-color-lighter] h-full overflow-y-auto scroll-my-24 w-full;
}

:deep(.el-divider__text) {
  @apply bg-[--el-fill-color-lighter] text-[--el-text-color-regular] font-normal;
}
</style>
